首页 / 技术类 / COM / 裸写一个进程内 COM 组件

裸写一个进程内 COM 组件

2012-09-07 00:23:00

引言

前几天山寨了ATL的COM_INTERFACE,了解了一个COM类的如何进行通用的组织。今天再来学习下COM协议,看看如何实现一个COM组件——当然,也是不能用ATL的,不然就学不到什么了。

COM DLL说简单简单,说复杂也很复杂。说简单呢,其实貌似只要导出下面这五个函数就可以了:

(我有点怀疑但不确定DllInstall是不是后来加的,本文中我们先不理它。)

前四个函数中,后两个是注册与反注册,就是写写注册表的事情,简单。前面两个,特别是DllGetClassObject,比较关键。

先不研究这些,我们先按前两天的方法写个COM类吧。

准备一个COM类

首先,我们定义一个接口 ISampleInterface,以及一个类CSampleClass Interface.h

 1#include <Unknwn.h>
 2
 3struct __declspec(uuid("{83C783E3-F989-4E0D-BFC5-631273EDFFDA}"))
 4ISampleInterface : public IUnknown
 5{
 6    STDMETHOD(SampleMethod)() PURE;
 7};
 8
 9class __declspec(uuid("{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}"))
10SampleClass;

与前面不同的是,这里两个声明都写上了一个UUID,前一个是接口ID(IID),后一个是类ID(CLSID)。这个文件将是提供给COM的使用者的。

SampleClass.h

 1class SampleClass : public xl::ComClass<SampleClass>,
 2                    xl::IUnknownImpl<ISampleInterface>
 3{
 4public:
 5    SampleClass();
 6    ~SampleClass();
 7
 8public:
 9    STDMETHOD(SampleMethod)();
10
11public:
12    XL_COM_INTERFACE_BEGIN(SampleClass)
13        XL_COM_INTERFACE(ISampleInterface)
14    XL_COM_INTERFACE_END()
15};

SampleClass.cpp

 1SampleClass::SampleClass()
 2{
 3    InterlockedIncrement(&g_nModuleCount);
 4}
 5
 6SampleClass::~SampleClass()
 7{
 8    InterlockedDecrement(&g_nModuleCount);
 9}
10
11STDMETHODIMP SampleClass::SampleMethod()
12{
13    MessageBox(NULL, _T("SampleMethod called."), _T("Info"), MB_OK | MB_ICONINFORMATION);
14    return S_OK;
15}

SampleMethod 就简单调用一个MessageBox意思一下。构造函数和析构函数中两行先不看,后面解释。

实现DllCanUnloadNow

这个函数的MSDN文档见: http://msdn.microsoft.com/en-us/library/windows/desktop/ms690368.aspx

函数原型为:

1HRESULT __stdcall DllCanUnloadNow(void);

函数实现的要求是,当此DLL内的所有COM对象都消亡了的时候,返回S_OK;如果还有COM对象存在,就返回S_FALSE。

现在我们有一个COM类SampleClass,它可能被创建一次,然后引用计数加加减减;也可能被创建多次,每个实例的引用计数同样会被加加减减。当引用计数被减为0的时候,对象将消亡(析构函数被调用)。因此,我们在对象的构造和析构的地方埋点就可以了。

定义一个全局变量:

1LONG g_nModuleCount = 0;

然后看到刚才的SampleClass的构造函数和析构函数中的灰色代码,就可以存对象的创建/销毁计数了。对于多个COM对象的情况,也可以这么搞。

然后,DllCanUnloadNow的实现就很简单了:

1STDAPI DllCanUnloadNow()
2{
3    return g_nModuleCount > 0 ? S_FALSE : S_OK;
4}

实现DllGetClassObject

终于到关键的地方了。以前一直弄不懂这个函数。它的MSDN文档见: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680760.aspx

函数原型为:

1HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv);

第一个参数是要创建的对象的CLSID,很明确。第二个参数有点迷惑,MSDN原文是:

A reference to the identifier of the interface that the caller is to use to communicate with the class object. Usually, this is IID_IClassFactory (defined in the OLE headers as the interface identifier for IClassFactory).

“Usually”,它是IID_IClassFactory。我不知道有没有不“Usually”的情况,也不知道这个接口原先的设计意图是什么。如果可能,其实完全可以绕开类厂机制,直接用想要使用的那个接口的IID,貌似整套机制也能运转……有木有达人解释下它的渊源?

不过呢,目前我就把它当作IID_IClassFactory,其他一律不支持。

我们先要实现一个“类厂”——一个继承于IClassFactory的COM类:

ClassFactory.h

 1class ClassFactory : public xl::ComClass<ClassFactory>,
 2                     public xl::IClassFactoryImpl<>
 3{
 4public:
 5    ClassFactory(REFCLSID rclsid);
 6    ~ClassFactory();
 7
 8public:
 9    STDMETHOD(CreateInstance)(_In_opt_ IUnknown *pUnkOuter,
10                              _In_ REFIID riid,
11                              _COM_Outptr_  void **ppvObject);
12
13public:
14    XL_COM_INTERFACE_BEGIN(ClassFactory)
15        XL_COM_INTERFACE(IClassFactory)
16    XL_COM_INTERFACE_END()
17
18private:
19    CLSID m_clsid;
20};

IClassFactory有两个方法,CreateInstance和LockServer。我们只实现前者,它用于创建一个对象,第一个参数不理它,第二个参数是IID,第三个参数用于输出。CLSID由构造函数传入,保存在m_clsid中——类厂是与COM类一一对应的。

ClassFactory.cpp

 1ClassFactory::ClassFactory(REFCLSID rclsid)
 2{
 3    m_clsid = rclsid;
 4}
 5
 6ClassFactory::~ClassFactory()
 7{
 8
 9}
10
11STDMETHODIMP ClassFactory::CreateInstance(_In_opt_ IUnknown *pUnkOuter,
12                                          _In_ REFIID riid,
13                                          _COM_Outptr_  void **ppvObject)
14{
15    if (riid == __uuidof(ISampleInterface) && m_clsid == __uuidof(SampleClass))
16    {
17        ISampleInterface *p = new SampleClass;
18        p->QueryInterface(riid, ppvObject);
19
20        return S_OK;
21    }
22
23    return CLASS_E_CLASSNOTAVAILABLE;
24}

这里只对IID为__uuidof(ISampleInterface)且CLSID为__uuidof(SampleClass))的情况作了响应,创建一个SampleClass对象。注意到创建对象之后有一次QueryInterface,在这里面会做一次AddRef操作,因此引用计数此时为1。其实AddRef可以不出现在代码中,需要的时候就用QueryInterface代替。

这些都准备好了,最后来实现DllGetClassObject:

 1STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID *ppv)
 2{
 3    if (riid == __uuidof(IClassFactory) && rclsid == __uuidof(SampleClass))
 4    {
 5        IClassFactory *p = new ClassFactory(rclsid);
 6        p->QueryInterface(riid, ppv);
 7
 8        return S_OK;
 9    }
10
11    return CLASS_E_CLASSNOTAVAILABLE;
12}

实现DllRegisterServer和DllUnregisterServer

这个我就直接贴代码了:

 1STDAPI DllRegisterServer(void)
 2{
 3    TCHAR szModulePath[MAX_PATH] = {};
 4    GetModuleFileName(g_hModule, szModulePath, ARRAYSIZE(szModulePath));
 5
 6    xl::Registry::SetString(HKEY_CLASSES_ROOT,
 7                            _T("CLSID\\{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}\\InprocServer32"),
 8                            _T(""),
 9                            szModulePath);
10
11    xl::Registry::SetString(HKEY_CLASSES_ROOT,
12                            _T("CLSID\\{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}\\ProgID"),
13                            _T(""),
14                            _T("Streamlet.COMProvider.SampleClass.1"));
15
16    xl::Registry::SetString(HKEY_CLASSES_ROOT,
17                            _T("Streamlet.COMProvider.SampleClass.1"),
18                            _T(""),
19                            _T("SampleClass Class"));
20
21    xl::Registry::SetString(HKEY_CLASSES_ROOT,
22                            _T("Streamlet.COMProvider.SampleClass.1\\CLSID"),
23                            _T(""),
24                            _T("{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}"));
25
26    return S_OK;
27}
28
29STDAPI DllUnregisterServer(void)
30{
31    xl::Registry::DeleteKeyRecursion(HKEY_CLASSES_ROOT,
32                                     _T("CLSID\\{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}"));
33
34    xl::Registry::DeleteKeyRecursion(HKEY_CLASSES_ROOT,
35                                     _T("Streamlet.COMProvider.SampleClass.1"));
36
37    return S_OK;
38}

注意,我这里并没有写得很全,只是注册几项必要的。至此,我们的COM组件实现完毕。

调用COM组件

编译刚才的DLL,并使用regsvr32注册(注意管理员权限)。然后写一个小程序来调用之:

 1#include <tchar.h>
 2#include <Objbase.h>
 3#include "../COMProvider/Interface.h"
 4
 5int _tmain(int argc, TCHAR *argv[])
 6{
 7    HRESULT hr = CoInitialize(NULL);
 8
 9    ISampleInterface *pSampleInterface = nullptr;
10    hr = CoCreateInstance(__uuidof(SampleClass),
11                          nullptr,
12                          CLSCTX_INPROC_SERVER,
13                          __uuidof(ISampleInterface),
14                          (LPVOID *)&pSampleInterface);
15
16    if (SUCCEEDED(hr))
17    {
18        pSampleInterface->SampleMethod();
19        pSampleInterface->Release();
20    }
21  
22    CoUninitialize();
23
24    return 0;
25}

运行结果:

上述例子代码见COMProtocol.rarhttp://pan.baidu.com/s/1c0GSI7u),库依赖见http://xllib.codeplex.com/。当然,现在只是简单地迎合了一下CoCreateInstance,还有许多其他事情要做,且听下回分解。


首发:http://www.cppblog.com/Streamlet/archive/2012/09/07/189762.html



NoteIsSite/0.4